package com.slz3.business.module.goodsManagement.gooInventory.service;

import com.slz3.business.frame.enums.AuditFlag;
import com.slz3.business.frame.enums.ValidFlag;
import com.slz3.business.frame.module.Repositorys;
import com.slz3.business.frame.module.Services;
import com.slz3.business.frame.redis.Jedis;
import com.slz3.business.frame.redis.RK;
import com.slz3.business.frame.response.RR;
import com.slz3.business.frame.steward.PageableSteward;
import com.slz3.business.frame.util.VE;
import com.slz3.business.module.goodsManagement.gooGoods.domain.GooGoods;
import com.slz3.business.module.goodsManagement.gooInventory.domain.GooInventory;
import com.slz3.business.module.goodsManagement.gooInventory.domain.GooInventoryFactory;
import com.slz3.business.module.goodsManagement.gooInventory.steward.GooInventorySpecification;
import com.slz3.business.module.orderManagement.ordOrder.domain.OrdOrder;
import com.slz3.business.module.orderManagement.ordOrderLine.domain.OrdOrderLine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.ServerRequest;

import java.util.*;
import java.util.function.Function;

import static java.util.stream.Collectors.*;

@Slf4j
@Service
public class GooInventoryServiceImpl implements GooInventoryService {
    @Override
    public GooInventory save(GooInventory gooInventory) {
        return Repositorys.gooInventory.save(GooInventoryFactory.initBaseFields(gooInventory));
    }

    @Override
    public Page<GooInventory> page(MultiValueMap<String, String> params) {
        try {
            Page<GooInventory> gooInventoryPage = Repositorys.gooInventory.findAll(
                    GooInventorySpecification.findJoin(params), PageableSteward.extract(params)
            );
//            // join
//            Map<UUID, GooGoods> gooGoodsMap = Repositorys.gooGoods.findAllById(
//                    gooInventoryPage.getContent().stream()
//                            .filter(item -> item.getFkGoods() != null)
//                            .map(GooInventory::getFkGoods)
//                            .collect(toList())
//            ).stream().collect(Collectors.toMap(GooGoods::getUuid, Function.identity()));
//            // merge
//            gooInventoryPage.getContent().stream().forEach(item -> item.setGooGoods(gooGoodsMap.get(item.getFkGoods())));
            return gooInventoryPage;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    @Override
    public GooInventory modify(GooInventory gooInventoryNew) {
        return Repositorys.gooInventory.saveAndFlush(GooInventoryFactory.initBaseFields(gooInventoryNew));
    }

    @Override
    public Optional<GooInventory> single(UUID uuid) {
        return Repositorys.gooInventory.findById(uuid);
    }

    @Override
    public List<GooInventory> findAllByUuids(Iterable<UUID> uuids) {
        return Repositorys.gooInventory.findAllById(uuids);
    }

    @Override
    public List<GooInventory> delete(List<GooInventory> gooInventoryList) {
        gooInventoryList.stream().forEach(item -> {
            item.setValidFlag(ValidFlag.DEL);
            GooInventoryFactory.initBaseFields(item);
        });
        return Services.gooInventoryTransactional.batchModify(gooInventoryList);
    }

    // gooInventoryList is not empty
    @Override
    public List<GooInventory> audit(ServerRequest req, List<GooInventory> gooInventoryList) {
        // temporary
        Map<UUID, Long> goodsInventoryAmountMap = new HashMap<>();
        // set status
        List<GooInventory> gooInventoryListNew = Services.gooInventoryTransactional.batchModify(
                gooInventoryList.stream()
                        .map(item -> GooInventoryFactory.newByAuditAuditStatus(item))
                        .filter(item -> item != null)
                        .collect(toList())
        );
        // check
        if (VE.isEmpty(gooInventoryListNew)) return null;
        // sync redis
        try {
            gooInventoryListNew.forEach(item -> {
                // redis get
                String redisKey = RK.SERVICE__WMS__SIMPLE__GOO_GOODS_UUID__$UUID$.replace("${uuid}", item.getFkGoods().toString());
                Integer inventoryAmount = Jedis.db0.get(redisKey);
                // redis set
                Long serAmountCurrentTotal = item.getSerAmount();
                if (inventoryAmount == null) {
                    Jedis.db0.set(redisKey, item.getSerAmount());
                } else {
                    serAmountCurrentTotal = Jedis.db0.incrAndDecr(redisKey, item.getSerAmount());
                }
                //
                goodsInventoryAmountMap.put(item.getFkGoods(), serAmountCurrentTotal);
            });
            // check
            if (VE.isEmpty(goodsInventoryAmountMap)) {
                RR.resDetail(req).put("audit", "change status success, but sync redis fail.");
                return null;
            }
        } catch (Exception e) {
            RR.resDetail(req).put("auditSyncRedisException", e.getMessage());
            return null;
        }
        // sync inventory
        try {
            List<GooInventory> list = gooInventoryListNew.stream()
                    .map(item -> GooInventoryFactory.newBySerAmountCurrentTotal(
                            item, goodsInventoryAmountMap.get(item.getFkGoods())
                    ))
                    .filter(item -> item != null)
                    .collect(toList());
            Services.gooInventoryTransactional.batchModify(list);
        } catch (Exception e) {
            RR.resDetail(req).put("auditSyncInventoryException", e.getMessage());
            return null;
        }
        // sync goods
        Services.gooInventory.sync();
        //
        return gooInventoryListNew;

    }

    @Override
    public void sync() {
        //
        List<GooInventory> tarInventoryList = Repositorys.gooInventory.findAll().stream()
                .filter(item -> ValidFlag.isValid(item.getValidFlag()))
                .filter(item -> AuditFlag.isApproved(item.getSerAudit()))
                .filter(item -> item.getFkGoods() != null)
                .filter(item -> item.getSerAmount() != null)
                .collect(toList());
        //
        List<OrdOrder> tarOrderList = Repositorys.ordOrder.findAll().stream()
                .filter(item -> ValidFlag.isValid(item.getValidFlag()))
                .filter(item -> item.getSerOrderStatus() != null && item.getSerOrderStatus() != 5)
                .collect(toList());
        Map<UUID, OrdOrder> tarOrderMap = tarOrderList.stream().collect(toMap(OrdOrder::getUuid, Function.identity()));
        List<OrdOrderLine> tarOrderLineList = Repositorys.ordOrderLine.findAll().stream()
                .filter(item -> tarOrderMap.get(item.getSerOrderId()) != null)
                .filter(item -> item.getSerOrderCount() != null)
                .collect(toList());
        //
        Map<UUID, Long> tarInventorySumAmountList = tarInventoryList.stream()
                .collect(groupingBy(
                        GooInventory::getFkGoods,
                        summingLong(GooInventory::getSerAmount)
                ));
        //
        Map<UUID, Long> tarOrderLineSumAmountList = tarOrderLineList.stream()
                .collect(groupingBy(
                        OrdOrderLine::getSerItemId,
                        summingLong(OrdOrderLine::getSerOrderCount)
                ));
        //
        Map<UUID, Long> tarSumAmountList = new HashMap<>();
        tarInventorySumAmountList.entrySet().stream().forEach(item ->
                tarSumAmountList.put(item.getKey(), item.getValue())
        );
        tarOrderLineSumAmountList.entrySet().stream().forEach(item -> {
            Long val = item.getValue() * -1;
            Long value = tarSumAmountList.get(item.getKey());
            if (value != null) {
                value += val;
                tarSumAmountList.put(item.getKey(), value);
            } else {
                tarSumAmountList.put(item.getKey(), val);
            }
        });
        // sync pg to redis
        if (VE.isNotEmpty(tarSumAmountList)) {
            // pg
            List<GooGoods> newGooGoodsList = Repositorys.gooGoods.findAll().stream()
//                    .filter(item -> tarSumAmountList.get(item.getUuid()) != null)
                    .map(item -> {
                        Long curAmount = tarSumAmountList.get(item.getUuid());
                        if (curAmount == null) {
                            item.setSerGoodsStock(
                                    0
                            );
                        } else {
                            item.setSerGoodsStock(
                                    curAmount.intValue()
                            );
                        }
                        return item;
                    }).collect(toList());
            Repositorys.gooGoods.saveAll(newGooGoodsList);
            // redis
            Jedis.db0.redisTemplate.opsForValue().multiSet(
                    tarSumAmountList.entrySet().stream().collect(toMap(
                            item -> RK.WMS.simple(item.getKey().toString()),
                            item -> item.getValue()
                    ))
            );
        }
    }

}
